package fr.cdj.thebox.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import fr.cdj.thebox.server.generated.GeneratedConf;
import fr.cdj.thebox.shared.FileList;
import fr.cdj.thebox.shared.FileMetaData;

public class FileManager {

	private GeneratedConf gConf;

	public FileManager(GeneratedConf gConf) {
		this.gConf = gConf;
	}

	public String securePath(String path) {
		return path.replace("/..", "").replace("\\..", "").replace("../", "").replace("..\\", "");
	}

	public String transformIntoValidPath(String path) {
		String initialPath = gConf.getPath();
		return path.length() < initialPath.length() ? null : path.substring(initialPath.length()).replace('\\', '/');
	}

	public FileList list(String path) throws FileNotFoundException {
		FileList output = new FileList();
		List<FileMetaData> filesMetaDatas = new ArrayList<FileMetaData>();

		String securedPath = securePath(gConf.getPath() + path);
		Path filePath = FileSystems.getDefault().getPath(securedPath);

		output.setPath(path);

		DirectoryStream<Path> stream = null;
		try {
			stream = Files.newDirectoryStream(filePath);
			for (Path childPath : stream) {
				boolean isHidden = Files.isHidden(childPath);
				if (gConf.isShowHidden() || !isHidden) {
					FileMetaData fileMetaData = new FileMetaData();
					boolean isDir = Files.isDirectory(childPath);
					fileMetaData.setDirectory(isDir);
					fileMetaData.setHidden(isHidden);
					fileMetaData.setLastModified(Files.getLastModifiedTime(childPath).toMillis());
					if (gConf.isDirLength()) {
						fileMetaData.setLength(getLength(childPath));
					} else {
						fileMetaData.setLength(isDir ? null : Files.size(childPath));
					}
					fileMetaData.setName(childPath.getFileName().toString());
					fileMetaData.setPath(transformIntoValidPath(childPath.toString()));
					filesMetaDatas.add(fileMetaData);
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (stream != null) {
					stream.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		output.setFilesMetaDatas(filesMetaDatas);

		return output;
	}

	private Long getLength(Path filePath) {
		final AtomicLong size = new AtomicLong(0);
		try {
			if (!Files.isDirectory(filePath)) {
				return Files.size(filePath);
			} else {
				Files.walkFileTree(filePath, new SimpleFileVisitor<Path>() {
					@Override
					public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
						size.addAndGet(attrs.size());
						return FileVisitResult.CONTINUE;
					}

					@Override
					public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
						return FileVisitResult.CONTINUE;
					}
				});
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return size.get();
	}

	public void zipFilesIntoStream(OutputStream out, String[] paths) throws IOException {
		List<File> filesToZip = new ArrayList<File>();

		for (String path : paths) {
			File fileFromPath = getFileFromPath(path);
			filesToZip.addAll(getAllFiles(fileFromPath));
		}

		ZipOutputStream zos = new ZipOutputStream(out);
		zos.setLevel(gConf.getZiplevel());

		for (File fileToZip : filesToZip) {
			zipFile(zos, fileToZip);
		}

		zos.closeEntry();
		zos.close();
	}

	public long zipFile(ZipOutputStream zos, File fileToZip) throws IOException {
		long bytesZipped = 0;
		if (fileToZip.isDirectory()) {
			for (File subFile : fileToZip.listFiles()) {
				bytesZipped += zipFile(zos, subFile);
			}
		} else {
			ZipEntry ze = new ZipEntry(transformIntoValidPath(fileToZip.getCanonicalPath()).substring(1));
			zos.putNextEntry(ze);
			FileInputStream in = new FileInputStream(fileToZip);

			byte[] buffer = new byte[1024];

			int len;
			while ((len = in.read(buffer)) > 0) {
				zos.write(buffer, 0, len);
				bytesZipped += len;
			}

			in.close();
		}
		return bytesZipped;
	}

	public void deleteFile(File file) {
		if (file.exists()) {
			if (file.isDirectory()) {
				for (File subFile : file.listFiles()) {
					deleteFile(subFile);
				}
			}
			file.delete();
		}
	}

	public File getFileFromPath(String reqFile) {
		return new File(securePath(gConf.getPath() + reqFile));
	}

	public List<File> getAllFiles(File file) {
		List<File> files = new ArrayList<File>();
		if (file.isFile()) {
			files.add(file);
		} else {
			for (File child : file.listFiles()) {
				files.addAll(getAllFiles(child));
			}
		}
		return files;
	}

}
